/**
 * \file foreign.cpp
 * \brief Foreign function call interface
 * \author Wouter Caarls <w.caarls@tnw.tudelft.nl>
 *
 * \verbatim
 * Copyright (c) 2005 Wouter Caarls, Delft University of Technology
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * \endverbatim
 */

#include <stdio.h>
#include <dlfcn.h>
#include <avcall.h>
#include <assert.h>

#include "Symbol.h"
#include "Expression.h"

typedef std::list<void*>::iterator HI;

std::list<void*> handles;

void loadLibrary(char *name)
{
  void *handle;

  handle = dlopen(name, RTLD_LAZY|RTLD_GLOBAL);
  if (!handle) handle = dlopen(CStr("lib") << name << ".so", RTLD_LAZY|RTLD_GLOBAL);

  if (handle)
  {
    handles.push_back(handle);
  }
  else
  {
    plog.err() << "Couldn't load dynamic library " << name << ": " << dlerror()
	       << std::endl;
    exit(-1);
  }
}

Value *callForeign(Function *fun, FunCall *call)
{
  FunCall::AI ii;
  IdDecl::PI jj;
  void *sym=NULL;
  std::vector<char*> cleanup;

  /* Find symbol */
  for (HI ii = handles.begin(); ii != handles.end(); ++ii)
  {
    if ((sym = dlsym((*ii), fun->identifier.c_str())))
    {
      plog.reduce() << HDR << "Calling foreign function " << fun->identifier << std::endl;
      break;
    }
  }

  if (!sym)
  {
    plog.debug() << HDR << "Foreign function " << fun->identifier << " not found"
		 << std::endl;
    return NULL;
  }

  /* RETURN TYPE MUST BE INTEGER OR FLOAT OR VOID */
  if (typeid(*call->type) != typeid(VoidType) &&
      typeid(*call->type) != typeid(IntegerType) &&
      typeid(*call->type) != typeid(FloatType))
  {
    plog.reduce() << HDR << "Return type " << call->type->toString()
		  << " not supported" << std::endl;

    return NULL;
  }

  /* ALL ARGUMENTS MUST BE KNOWN INTEGERS, FLOATS, OR ARRAYS OF INTEGERS OR FLOATS */
  for (ii=call->arguments.begin(); ii != call->arguments.end(); ++ii)
  {
    if (!(*ii)->known)
    {
      plog.reduce() << HDR << "One or more arguments are unknown" << std::endl;

      return NULL;
    }

    if (typeid(**ii) != typeid(IntegerValue) &&
	typeid(**ii) != typeid(FloatValue) &&
	typeid(**ii) != typeid(ArrayValue))
    {
      plog.err() << HDR << (*ii)->toString() << std::endl;


      plog.reduce() << HDR << "Argument type " << (*ii)->type->toString()
		    << " not supported" << std::endl;
      
      return NULL;
    }

    ArrayValue *aval = dynamic_cast<ArrayValue*>(*ii);

    if (aval)
    {
      PointerType *ptype = dynamic_cast<PointerType*>(aval->type);

      if (!ptype)
	error("Internal type mismatch");

      IntegerType *itype = dynamic_cast<IntegerType*>(ptype->to);

      if (!itype || itype->precision != 8 || !ptype->elements)
      {
	plog.reduce() << HDR << "Argument array type " << ptype->to->toString()
		      << " not supported" << std::endl;
	
	return NULL;
      }
    }
  }

  /* Start function argument list with the appropriate return type */
  av_alist alist;
  unsigned char resdata[16];

  if (typeid(*call->type) == typeid(IntegerType))
  {
    IntegerType *itype = dynamic_cast<IntegerType*>(call->type);

    //printf("returns integer, %d bits\n", itype->precision);

    switch (itype->precision)
    {
      case 7: av_start_schar(alist, sym, resdata); break;
      case 8: av_start_uchar(alist, sym, resdata); break;
      case 15: av_start_short(alist, sym, resdata); break;
      case 16: av_start_ushort(alist, sym, resdata); break;
      case 31: av_start_int(alist, sym, resdata); break;
      case 32: av_start_uint(alist, sym, resdata); break;
      default:
	error("Unsupported integer return type");
    }
  }
  else if (typeid(*call->type) == typeid(FloatType))
  {
    FloatType *ftype = dynamic_cast<FloatType*>(call->type);

    //printf("returns float, %d bits\n", ftype->precision);

    switch (ftype->precision)
    {
      case 32: av_start_float(alist, sym, resdata); break;
      case 64: av_start_double(alist, sym, resdata); break;
      default:
	error("Unsupported floating point return type");
    }
  }
  else
    av_start_void(alist, sym);

  /* Add arguments */
  for (ii=call->arguments.begin(), jj = fun->data->iddecl->params.begin();
       ii != call->arguments.end() && jj != fun->data->iddecl->params.end(); ++ii, ++jj)
  {
    Value *val = dynamic_cast<Value*>(*ii);
    assert(val != NULL);

    IntegerType *itype = dynamic_cast<IntegerType*>((*jj)->iddecl->type);
    FloatType *ftype = dynamic_cast<FloatType*>((*jj)->iddecl->type);
    PointerType *ptype = dynamic_cast<PointerType*>((*jj)->iddecl->type);

    int r=0;

    if (itype)
    {
      //printf("integer, %d bits\n", itype->precision);

      switch (itype->precision)
      {
	case 7: r=av_schar(alist, (signed char)val->castToFloat()); break;
	case 8: r=av_uchar(alist, (unsigned char)val->castToFloat()); break;
	case 15: r=av_short(alist, (signed short)val->castToFloat()); break;
	case 16: r=av_ushort(alist, (unsigned short)val->castToFloat()); break;
	case 31: r=av_int(alist, (signed int)val->castToFloat()); break;
	case 32: r=av_uint(alist, (unsigned int)val->castToFloat()); break;
	default:
	  error("Unsupported integer argument type");
      }
    }
    else if (ftype)
    {
      //printf("float, %d bits\n", ftype->precision);

      switch (ftype->precision)
      {
	case 32: r=av_float(alist, (float)val->castToFloat()); break;
	case 64: r=av_double(alist, (double)val->castToFloat()); break;
	default:
	  error("Unsupported floating point argument type");
      }
    }
    else if (ptype)
    {
      /* String */
      ArrayValue *aval = dynamic_cast<ArrayValue*>(*ii);
      assert(aval != NULL);

      PointerType *atype = dynamic_cast<PointerType*>(aval->type);
      assert(atype != NULL);

      char *data = (char*) malloc(atype->elements);
      int kk;      
 
      for (kk=0; kk != atype->elements; ++kk)
      {
	IntegerValue *element = dynamic_cast<IntegerValue*>(*aval->data[kk]);
	assert(element != NULL);

	data[kk] = element->data;
      }

      r = av_ptr(alist, char*, data);

      cleanup.push_back(data);
    }
    else
      error("Unsupported argument type");

    if (r < 0)
      error("Couldn't push argument");
  }

/*
  if (ii != call->arguments.end() || jj != fun->data->iddecl->params.end())
    error("Argument count mismatch");
*/

  /* Call function */
  if (av_call(alist))
    error("Foreign function call failed");
  
  Value *res;

  if (typeid(*call->type) == typeid(IntegerType))
  {
    IntegerValue *ires = new IntegerValue(0);
    IntegerType *itype = dynamic_cast<IntegerType*>(call->type);

    ires->type = call->type;
    res = ires;

    switch (itype->precision)
    {
      case 7: ires->data = *((signed char*) resdata); break;
      case 8: ires->data = *((unsigned char*) resdata); break;
      case 15: ires->data = *((signed short*) resdata); break;
      case 16: ires->data = *((unsigned short*) resdata); break;
      case 31: ires->data = *((signed int*) resdata); break;
      case 32: ires->data = *((unsigned int*) resdata); break;
      default:
	error("Unsupported integer return type");
    }
  }
  else if (typeid(*call->type) == typeid(FloatType))
  {
    FloatValue *fres = new FloatValue(0);
    FloatType *ftype = dynamic_cast<FloatType*>(call->type);

    fres->type = call->type;
    res = fres;

    switch (ftype->precision)
    {
      case 32: fres->data = *((float*) resdata); break;
      case 64: fres->data = *((double*) resdata); break;
      default:
	error("Unsupported floating point return type");
    }
  }
  else
    res = new VoidValue();

  plog.debug() << HDR << "Result is " << res->toString() << std::endl;

  /* Clean up */
  std::vector<char*>::iterator ci;

  if (!cleanup.empty())
    for (ci=cleanup.begin(); ci != cleanup.end(); ++ci)
      free(*ci);

  /* Return result */
  return res;
}
